package com.top10.utils;

import com.top10.bean.wxpay.PayRequest;
import com.top10.bean.wxpay.RefundRequest;
import com.top10.bean.wxpay.WxPayRefundRequest;
import com.top10.bean.wxpay.WxPayUnifiedorderRequest;
import com.top10.common.WXConst;
import com.top10.common.WXPayConst;
import com.top10.enums.PayTypeEnum;
import com.top10.enums.PostTypeEnum;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.top10.utils.WxPaySignatureUtil.generateJSAPISignature;
import static com.top10.utils.WxPaySignatureUtil.sign;
import static com.top10.utils.XmlUtil.xmlToMap;

public class WxPayUtil {

    public static void main(String[] args) throws Exception {
        PayRequest request = new PayRequest();
        request.setOrderAmount(1);
        request.setPayTypeEnum(PayTypeEnum.WXPAY_NATIVE);
        request.setNotifyUrl(WXPayConst.ORDER_NOTIFYURL);
//        request.setSpbillCreateIp("14.23.183.32");
        request.setOrderName("测试扫码支付");
        String outTradeNo = WXUtil.getOutTradeNo();
        System.out.println("===>"+outTradeNo);
        request.setOutTradeNo(outTradeNo);

        Map<String, String> resultMap = pay(request, "");
        System.out.println("===>>"+resultMap);

    }

    /**
     * 微信支付
     *
     * @param request
     * @param jsUrl
     * @return
     * @throws Exception
     */
    public static Map<String, String> pay(PayRequest request, String jsUrl) throws Exception {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.MINUTE, 2);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String timeExpire = sdf.format(calendar.getTime());

        WxPayUnifiedorderRequest wxRequest = new WxPayUnifiedorderRequest();

        wxRequest.setAppid(WXConst.APPID);
        wxRequest.setMchId(WXPayConst.MCHID);
        wxRequest.setNonceStr(RandomUtils.getRandomStr());
        wxRequest.setBody(request.getOrderName());
        wxRequest.setOutTradeNo(request.getOutTradeNo());
        wxRequest.setTotalFee(request.getOrderAmount());
        wxRequest.setSpbillCreateIp(request.getSpbillCreateIp() == null || request.getSpbillCreateIp().isEmpty() ? "8.8.8.8" : request.getSpbillCreateIp());
        wxRequest.setNotifyUrl(request.getNotifyUrl());
        wxRequest.setTradeType(switchH5TradeType(request.getPayTypeEnum()));
        wxRequest.setOpenid(request.getOpenid());
//        wxRequest.setTimeExpire(timeExpire);
        wxRequest.setSign(sign(xmlToMap(XmlUtil.toXMl(wxRequest)), WXPayConst.MCHKEY));


        String xml = XmlUtil.toXMl(wxRequest);

        String result = HttpClientUtil.post(WXPayConst.WXPAY_UNIFIEDORDER_GATEWAY, xml, PostTypeEnum.XML);

        Map<String, String> map = handleReturnMessage(result, jsUrl);

        return map;
    }

/**
     * 退款
     * @param refundRequest 请求
     * @param password  退款密码
     * @param key       商户密钥
     * @return
     * @throws Exception
     */
    public static Map<String, String> refund(RefundRequest refundRequest, String certificatePath, String password, String key) throws  Exception{

        WxPayRefundRequest wxPayRefundRequest = new WxPayRefundRequest();

        wxPayRefundRequest.setAppid(WXConst.APPID);
        wxPayRefundRequest.setMchId(WXPayConst.MCHID);
        wxPayRefundRequest.setNonceStr(RandomUtils.getRandomStr());
        wxPayRefundRequest.setTotalFee(refundRequest.getOrderAmount());
        wxPayRefundRequest.setRefundFee(refundRequest.getRefundFee());
        wxPayRefundRequest.setOutRefundNo(refundRequest.getOutRefundNo());

        wxPayRefundRequest.setOutTradeNo(refundRequest.getOutTradeNo());
//        wxPayRefundRequest.setTransactionId(refundRequest.getTransactionId());
        String sendMessage = XmlUtil.toXMl(wxPayRefundRequest);
        Map<String, String> mapdata = xmlToMap(sendMessage);

        wxPayRefundRequest.setSign(sign(mapdata,key));

        sendMessage = XmlUtil.toXMl(wxPayRefundRequest);
       // System.out.println(sendMessage);
        String result =  HttpClientUtil.executeBySslPost(WXPayConst.WXPAY_REFUND_GATEWAY,sendMessage,certificatePath,password);
        MyLog.RefoundLog.logRefound(result);

        return xmlToMap(result);
    }




    /*
     *
     * H5支付交易类型选择

     */

    public static String switchH5TradeType(PayTypeEnum payTypeEnum) {
        String tradeType = "JSAPI";
        switch (payTypeEnum) {
            case JSAPI:
                tradeType = "JSAPI";
                break;
            case WXPAY_MWEB:
                tradeType = "MWEB";
                break;
            case WXPAY_APP:
                tradeType = "APP";
                break;
            case WXPAY_NATIVE:
                tradeType = "NATIVE";
                break;
        }
        return tradeType;
    }


    /**
     * 最后返回给前端的数据
     *
     * @param returnMessage
     * @param jsUrl
     * @return
     * @throws Exception
     */

    public static Map<String, String> handleReturnMessage(String returnMessage, String jsUrl) throws Exception {
        Map returnMap = xmlToMap(returnMessage);
        Map<String, String> resultMap = new HashMap<String, String>();
        String return_code = (String) returnMap.get("return_code");
        String result_code = (String) returnMap.get("result_code");
        System.out.println(returnMap);
        if (return_code.equals(WXPayConst.FAIL)) {//不成功返回结果为空
            MyLog.TradeLog.errorBuy("ERROR  " + returnMessage);
            resultMap.put("error", (String) returnMap.get("return_msg"));
            return resultMap;
        }

        if (result_code.equals(WXPayConst.FAIL)) {
            MyLog.TradeLog.errorBuy("ERROR  " + returnMessage);
            resultMap.put("error", (String) returnMap.get("err_code_des"));
            return resultMap;
        }

        String tradeType = (String) returnMap.get("trade_type");

        //如果返回的是APP支付
        if (tradeType.equals("APP")) {
            String appId = (String) returnMap.get("appid");
            String partnerId = WXPayConst.MCHID;
            String perpayId = (String) returnMap.get("prepay_id");
            String packageValue = "Sign=WXPay";
            String nonceStr = RandomUtils.getRandomStr();
            String timeStamp = String.valueOf(WXUtil.getSecondTimestamp());

            resultMap.put("appid", appId);
            resultMap.put("partnerid", partnerId);
            resultMap.put("prepayid", perpayId);
            resultMap.put("package", packageValue);
            resultMap.put("noncestr", nonceStr);
            resultMap.put("timestamp", timeStamp);
            String sign = WxPaySignatureUtil.sign(resultMap, WXPayConst.MCHKEY);
            resultMap.put("sign", sign);

            return resultMap;
        }

        if (tradeType.equals("JSAPI")) {//返回的公众号支付，，
            String perpayId = (String) returnMap.get("prepay_id");
            String appId = (String) returnMap.get("appid");
            String signType = "MD5";

            String jsapi_ticket = WXConst.JSAPITICKET;
            String nonceStr = UUID.randomUUID().toString();
            String timestamp = String.valueOf(WXUtil.getSecondTimestamp());
            if (jsUrl == null) {
                MyLog.TradeLog.errorBuy("ERROR  jsUrl为空" + returnMessage);
                return null;
            }
            ;
            //微信签名
            Map<String, String> JSAPIMap = new HashMap<String, String>();
            JSAPIMap.put("jsapi_ticket", jsapi_ticket);
            JSAPIMap.put("noncestr", nonceStr);
            JSAPIMap.put("timestamp", timestamp);
            JSAPIMap.put("url", jsUrl);
            String signature = generateJSAPISignature(JSAPIMap);
            // 支付签名
            Map<String, String> JSAPIPayMap = new HashMap<String, String>();
            JSAPIPayMap.put("appId", appId);
            JSAPIPayMap.put("timeStamp", timestamp);
            JSAPIPayMap.put("nonceStr", nonceStr);
            JSAPIPayMap.put("package", "prepay_id=" + perpayId); //prepay_id=wx201710301549453bd02ae87d0526670466
            JSAPIPayMap.put("signType", "MD5");
            String paySign = sign(JSAPIPayMap, WXPayConst.MCHKEY);

            //返回结果
            resultMap.put("appId", appId);
            resultMap.put("timestamp", timestamp);
            resultMap.put("nonceStr", nonceStr);
            resultMap.put("signature", signature);
            resultMap.put("package", "prepay_id=" + perpayId);
            resultMap.put("signType", "MD5");
            resultMap.put("paySign", paySign);
            return resultMap;

        }
        if (tradeType.equals("MWEB")) {//返回的是H5支付
            resultMap.put("mweb_url", (String) returnMap.get("mweb_url"));
            return resultMap;
        }
        if (tradeType.equals("NATIVE")) {//返回的是扫码支付
            resultMap.put("code_url", (String) returnMap.get("code_url"));
            return resultMap;
        }
        return null;
    }


    /**
     * 异步通知 成功返回Map，失败返回null
     *
     * @param request
     * @param response
     * @return
     */

    public static Map<String, String> asynNotify(HttpServletRequest request, HttpServletResponse response) {
        ServletInputStream sis = null;
        String xmlData = "";
        Map<String, String> returnMap = null;
        try {
            //编码格式改成UTF-8
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/xml;charset=utf-8");
            response.setHeader("Cache-control", "no-cache");

            sis = request.getInputStream();
            // 取HTTP请求流长度
            int size = request.getContentLength();
            // 用于缓存每次读取的数据
            byte[] buffer = new byte[size];
            // 用于存放结果的数组
            byte[] xmldataByte = new byte[size];
            int count = 0;
            int rbyte = 0;
            // 循环读取
            while (count < size) {
                // 每次实际读取长度存于rbyte中
                rbyte = sis.read(buffer);
                for (int i = 0; i < rbyte; i++) {
                    xmldataByte[count + i] = buffer[i];
                }
                count += rbyte;
            }
            xmlData = new String(xmldataByte, "UTF-8");

            PrintWriter out = response.getWriter();
            out.println(WXPayConst.NOTIFY_RESPONSE_BODY);
            returnMap = xmlToMap(xmlData);
            String return_code = (String) returnMap.get("return_code");
            if (return_code.equals(WXPayConst.FAIL)) {//不成功返回结果为空
                return null;
            }


            //验签
            if (return_code.equals(WXPayConst.SUCCESS)) {
                if (!WxPaySignatureUtil.verify(returnMap, WXPayConst.MCHKEY)) {//验签失败
                    return null;
                }
                MyLog.TradeLog.logBuy("微信支付验签成功！信息：" + xmlData);
            }

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }


        return returnMap;
    }

    /**
     * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
     *
     * @param data     待签名数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, WXPayConst.SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(WXPayConst.FIELD_SIGN)) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空，则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if (WXPayConst.SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        } else if (WXPayConst.SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        } else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }

    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     *
     * @param data 待处理数据
     * @param key  密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }
}
